module WsClient
	module Ietf00
	
		class Handshake
			attr_reader :headers, :errors
			attr_accessor :body, :url, :challenge
		
			# Argument options can contain following keys
			# :origin => origin string
			# :protocol => subprotocol to use
			# :headers => extra headers hash
			def initialize(url, options)
				num1, key1 = generate_key
				num2, key2 = generate_key
			
				headers = {
					'Upgrade' => 'WebSocket',
					'Connection' => 'Upgrade',
					'Host' => "#{url.host}" + (":#{url.port.to_s}" || ''),
					'Origin' => options[:origin],
					'Sec-WebSocket-Key1' => key1,
					'Sec-WebSocket-Key2' => key2
				}
				
				headers['Sec-WebSocket-Protocol'] = options[:protocol] if options[:protocol]
				@headers = WsClient::HeadersHash.new(headers)
				
				@headers.update(options[:headers] || {})
				
				@url = url
				@body = random_bytes(8)
				
				bytestr = proc { |i| [i].pack('N') }
				
				@challenge = Digest::MD5.digest(bytestr[num1] + bytestr[num2] + @body)
				
				@errors = []
			end
			
			def [](name)
				@headers[name]
			end
			
			def []=(name, value)
				@headers[name] = value
			end
			
			def valid_response?(response)
				@errors = []
				
				if not response.valid?
					@errors << "Invalid response format"
				elsif not url.match?(response.headers['Sec-Websocket-Location'])
					@errors << "Location mismatch"
				elsif response.headers['Sec-WebSocket-Protocol'] != @headers['Sec-WebSocket-Protocol']
					@errors << "Sub-protocol mismatch"
				elsif @challenge != response.body
					@errors << "Challenge mismatch"
				end
				
				@errors.empty?
			end
		
			def to_s
				head = ["GET #{@url.resource_name} HTTP/1.1"]
				@headers.to_hash.each_pair do |name, value|
					head << "#{name}: #{value}"
				end
				
				head << "\r\n"
				
				handshake = head.join("\r\n")
				handshake << @body
				
				handshake
			end
			
			private
			
			def generate_key
				spaces = 1 + rand(12)
				max = 4294967295 / spaces
				number = rand(max + 1)
				product = number * spaces
				
				key = product.to_s
				
				chars = random_chars(1 + rand(12))
				chars.each do |c|
					i = rand(key.length + 1)
					key.insert(i, c)
				end
				
				spaces.times do
					i = rand(key.length - 1) + 1
					key.insert(i, ' ')
				end
				
				return number, key
			end
			
			def random_chars(num)
				chars = [("\u0021".."\u002f"), ("\u003a".."\u007e")].map { |r| r.to_a }.flatten
				chars.sort_by { rand }
				chars[0, num]
			end
			
			def random_bytes(num)
				num.times.map { rand(256) }.pack('C*')
			end
		end
	
	end
end
